package hos.retrofit;

import static java.util.Collections.unmodifiableList;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

import jdk.vm.ci.code.site.Call;

/**
 * <p>Title: Retrofit </p>
 * <p>Description:  </p>
 * <p>Company: www.mapuni.com </p>
 *
 * @author : 蔡俊峰
 * @version : 1.0
 * @date : 2024-04-12 20:59
 */
public class Retrofit {

    private final String baseUrl;
    private final String baseUrlEnd;
    // 命名空间
    private final String nameSpace;
    /**
     * 请求Header
     */
    private final Map<String, String> header;
    /**
     * 请求参数
     */
    private final Map<String, Object> parameters;
    private final ConcurrentHashMap<Method, Object> serviceMethodCache = new ConcurrentHashMap<>();

    private final List<RetrofitCallAdapter.Factory> callAdapterFactories;

    private final List<RetrofitConverter.Factory> converterFactories;
    /**
     * 请求拦截器
     */
    private final RetrofitInterceptor.RequestInterceptor requestInterceptor;
    /**
     * 响应拦截器
     */
    private final RetrofitInterceptor.ResponseInterceptor responseInterceptor;
    /**
     * 异常拦截器
     */
    private final RetrofitInterceptor.ExceptionInterceptor exceptionInterceptor;

    private final RetrofitCall.Factory retrofitCallFactory;

    private final ExecutorRun callBackExecutor;


    Retrofit(RetrofitCall.Factory retrofitCallFactory, String baseUrl, String baseUrlEnd,
             String nameSpace, Map<String, String> header,
             Map<String, Object> parameters,
             RetrofitInterceptor.RequestInterceptor requestInterceptor,
             RetrofitInterceptor.ResponseInterceptor responseInterceptor,
             RetrofitInterceptor.ExceptionInterceptor exceptionInterceptor,
             List<RetrofitConverter.Factory> converterFactories,
             List<RetrofitCallAdapter.Factory> callAdapterFactories,
             ExecutorRun callBackExecutor) {
        this.retrofitCallFactory = retrofitCallFactory;
        this.baseUrl = baseUrl;
        this.baseUrlEnd = baseUrlEnd;
        this.nameSpace = nameSpace;
        this.header = header;
        this.parameters = parameters;
        this.requestInterceptor = requestInterceptor;
        this.responseInterceptor = responseInterceptor;
        this.exceptionInterceptor = exceptionInterceptor;
        this.converterFactories = converterFactories;
        this.callAdapterFactories = callAdapterFactories;
        this.callBackExecutor = callBackExecutor;
    }

    public RetrofitCall.Factory getRetrofitCallFactory() {
        return retrofitCallFactory;
    }

    public ExecutorRun getCallBackExecutor() {
        return callBackExecutor;
    }

    public Retrofit addCallAdapterFactory(RetrofitCallAdapter.Factory factory) {
        callAdapterFactories.add(factory);
        return this;
    }

    public RetrofitCallAdapter<?, ?> callAdapter(Type returnType, Annotation[] annotations) {
        for (RetrofitCallAdapter.Factory callAdapterFactory : callAdapterFactories) {
            RetrofitCallAdapter<?, ?> adapter = callAdapterFactory.get(returnType, annotations, this);
            if (adapter != null) {
                return adapter;
            }
        }
        StringBuilder builder =
                new StringBuilder("Could not locate call adapter for ").append(returnType).append(".\n");
        builder.append("  Tried:");
        for (int i = 0, count = callAdapterFactories.size(); i < count; i++) {
            builder.append("\n   * ").append(callAdapterFactories.get(i).getClass().getName());
        }
        throw new IllegalArgumentException(builder.toString());
    }

    public <T, R> RetrofitConverter<T, R> converter(
            Type type, Annotation[] annotations) {
        Objects.requireNonNull(type, "type == null");
        Objects.requireNonNull(annotations, "annotations == null");
        for (RetrofitConverter.Factory converterFactory : converterFactories) {
            @SuppressWarnings("unchecked") RetrofitConverter<T, R> converter =
                    (RetrofitConverter<T, R>) converterFactory.get(type, annotations, this);
            if (converter != null) {
                return converter;
            }
        }
        StringBuilder builder =
                new StringBuilder("Could not locate ResponseBody converter for ")
                        .append(type)
                        .append(".\n");
        builder.append("  Tried:");
        for (int i = 0, count = converterFactories.size(); i < count; i++) {
            builder.append("\n   * ").append(converterFactories.get(i).getClass().getName());
        }
        throw new IllegalArgumentException(builder.toString());
    }

    public String getBaseUrl() {
        return baseUrl;
    }

    public String getBaseUrlEnd() {
        return baseUrlEnd;
    }

    public String getNameSpace() {
        return nameSpace;
    }

    public Map<String, String> getHeader() {
        return header;
    }

    public Map<String, Object> getParameters() {
        return parameters;
    }

    public ConcurrentHashMap<Method, Object> getServiceMethodCache() {
        return serviceMethodCache;
    }

    public List<RetrofitCallAdapter.Factory> getCallAdapterFactories() {
        return callAdapterFactories;
    }

    public List<RetrofitConverter.Factory> getConverterFactories() {
        return converterFactories;
    }

    public RetrofitInterceptor.RequestInterceptor getRequestInterceptor() {
        return requestInterceptor;
    }

    public RetrofitInterceptor.ResponseInterceptor getResponseInterceptor() {
        return responseInterceptor;
    }

    public RetrofitInterceptor.ExceptionInterceptor getExceptionInterceptor() {
        return exceptionInterceptor;
    }

    /**
     * @noinspection unchecked
     */
    public <T> T create(final Class<T> service) {
        return (T)
                Proxy.newProxyInstance(
                        service.getClassLoader(),
                        new Class<?>[]{service},
                        new InvocationHandler() {
                            private final Object[] emptyArgs = new Object[0];

                            @Override
                            public Object invoke(Object proxy, Method method, Object[] args)
                                    throws Throwable {
                                // If the method is a method from Object then defer to normal invocation.
                                if (method.getDeclaringClass() == Object.class) {
                                    return method.invoke(this, args);
                                }
                                args = args != null ? args : emptyArgs;
                                return loadServiceMethod(service, method).invoke(proxy, args);
                            }
                        });
    }

    /**
     * 缓存获取请求方法
     */
    @SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter")
    ServiceMethod<?> loadServiceMethod(Class<?> service, Method method) {
        while (true) {
            // Note: Once we are minSdk 24 this whole method can be replaced by computeIfAbsent.
            Object lookup = serviceMethodCache.get(method);

            if (lookup instanceof ServiceMethod<?>) {
                // Happy path: method is already parsed into the model.
                return (ServiceMethod<?>) lookup;
            }

            if (lookup == null) {
                // Map does not contain any value. Try to put in a lock for this method. We MUST synchronize
                // on the lock before it is visible to others via the map to signal we are doing the work.
                Object lock = new Object();
                synchronized (lock) {
                    lookup = serviceMethodCache.putIfAbsent(method, lock);
                    if (lookup == null) {
                        // On successful lock insertion, perform the work and update the map before releasing.
                        // Other threads may be waiting on lock now and will expect the parsed model.
                        ServiceMethod<Object> result;
                        try {
                            result = ServiceMethod.parseAnnotations(this, service, method);
                        } catch (Throwable e) {
                            // Remove the lock on failure. Any other locked threads will retry as a result.
                            serviceMethodCache.remove(method);
                            throw e;
                        }
                        serviceMethodCache.put(method, result);
                        return result;
                    }
                }
            }

            // Either the initial lookup or the attempt to put our lock in the map has returned someone
            // else's lock. This means they are doing the parsing, and will update the map before
            // releasing
            // the lock. Once we can take the lock, the map is guaranteed to contain the model or null.
            // Note: There's a chance that our effort to put a lock into the map has actually returned a
            // finished model instead of a lock. In that case this code will perform a pointless lock and
            // redundant lookup in the map of the same instance. This is rare, and ultimately harmless.
            synchronized (lookup) {
                Object result = serviceMethodCache.get(method);
                if (result == null) {
                    // The other thread failed its parsing. We will retry (and probably also fail).
                    continue;
                }
                return (ServiceMethod<?>) result;
            }
        }
    }

    public Builder newBuilder() {
        return new Builder(this);
    }

    public static class Builder {

        private String baseUrl;
        private String baseUrlEnd;
        // 命名空间
        private String nameSpace;
        /**
         * 请求Header
         */
        private Map<String, String> header;
        /**
         * 请求参数
         */
        private Map<String, Object> parameters;

        private final List<RetrofitCallAdapter.Factory> callAdapterFactories = new ArrayList<>();

        private final List<RetrofitConverter.Factory> converterFactories = new ArrayList<>();
        /**
         * 请求拦截器
         */
        private RetrofitInterceptor.RequestInterceptor requestInterceptor;
        /**
         * 响应拦截器
         */
        private RetrofitInterceptor.ResponseInterceptor responseInterceptor;
        /**
         * 异常拦截器
         */
        private RetrofitInterceptor.ExceptionInterceptor exceptionInterceptor;

        private final RetrofitCall.Factory retrofitCallFactory;

        private ExecutorRun callBackExecutor;

        public Builder(RetrofitCall.Factory retrofitCallFactory) {
            this.retrofitCallFactory = retrofitCallFactory;
        }

        Builder(Retrofit retrofit) {
            retrofitCallFactory = retrofit.retrofitCallFactory;
            baseUrl = retrofit.baseUrl;
            nameSpace = retrofit.nameSpace;
            converterFactories.addAll(retrofit.converterFactories);
            callAdapterFactories.addAll(retrofit.callAdapterFactories);
            if (retrofit.header != null) {
                header = new LinkedHashMap<>();
                header.putAll(retrofit.header);
            }
            if (retrofit.parameters != null) {
                parameters = new LinkedHashMap<>();
                parameters.putAll(retrofit.parameters);
            }
            requestInterceptor = retrofit.requestInterceptor;
            responseInterceptor = retrofit.responseInterceptor;
            exceptionInterceptor = retrofit.exceptionInterceptor;
            callBackExecutor = retrofit.callBackExecutor;
        }

        /**
         * Set the API base URL.
         */
        public Builder baseUrl(String baseUrl) {
            Objects.requireNonNull(baseUrl, "baseUrl == null");
            this.baseUrl = baseUrl;
            return this;
        }

        /**
         * Set the API base URL.
         */
        public Builder baseUrlEnd(String baseUrlEnd) {
            Objects.requireNonNull(baseUrl, "baseUrlEnd == null");
            this.baseUrlEnd = baseUrlEnd;
            return this;
        }

        public Builder nameSpace(String nameSpace) {
            Objects.requireNonNull(nameSpace, "nameSpace == null");
            this.nameSpace = nameSpace;
            return this;
        }

        public Builder header(Map<String, String> header) {
            if (this.header == null) {
                this.header = new LinkedHashMap<>();
            }
            this.header.putAll(header);
            return this;
        }

        public Builder header(String key, String value) {
            if (this.header == null) {
                this.header = new LinkedHashMap<>();
            }
            this.header.put(key, value);
            return this;
        }

        public Builder param(Map<String, Object> param) {
            if (this.parameters == null) {
                this.parameters = new LinkedHashMap<>();
            }
            this.parameters.putAll(param);
            return this;
        }

        public Builder param(String key, Object value) {
            if (this.parameters == null) {
                this.parameters = new LinkedHashMap<>();
            }
            this.parameters.put(key, value);
            return this;
        }


        public Builder interceptor(RetrofitInterceptor.RequestInterceptor responseInterceptor) {
            this.requestInterceptor = responseInterceptor;
            return this;
        }

        public Builder interceptor(RetrofitInterceptor.ResponseInterceptor responseInterceptor) {
            this.responseInterceptor = responseInterceptor;
            return this;
        }


        public Builder interceptor(RetrofitInterceptor.ExceptionInterceptor exceptionInterceptor) {
            this.exceptionInterceptor = exceptionInterceptor;
            return this;
        }

        /**
         * Add converter factory for serialization and deserialization of objects.
         */
        public Builder addConverterFactory(RetrofitConverter.Factory factory) {
            converterFactories.add(Objects.requireNonNull(factory, "factory == null"));
            return this;
        }

        /**
         * Add a call adapter factory for supporting service method return types other than {@link
         * Call}.
         */
        public Builder addCallAdapterFactory(RetrofitCallAdapter.Factory factory) {
            callAdapterFactories.add(Objects.requireNonNull(factory, "factory == null"));
            return this;
        }

        public Builder callbackExecutor(ExecutorRun executor) {
            this.callBackExecutor = Objects.requireNonNull(executor, "executor == null");
            return this;
        }


        /**
         * Returns a modifiable list of call adapter factories.
         */
        public List<RetrofitCallAdapter.Factory> callAdapterFactories() {
            return this.callAdapterFactories;
        }

        /**
         * Returns a modifiable list of converter factories.
         */
        public List<RetrofitConverter.Factory> converterFactories() {
            return this.converterFactories;
        }

        public Retrofit build() {
            if (baseUrl == null) {
                throw new IllegalStateException("Base URL required.");
            }
            if (retrofitCallFactory == null) {
                throw new IllegalStateException("retrofitCallFactory required.");
            }
            if (callBackExecutor == null) {
                throw new IllegalStateException("callBackExecutor required.");
            }
            // Make a defensive copy of the adapters and add the default Call adapter.
            List<RetrofitCallAdapter.Factory> callAdapterFactories = new ArrayList<>(this.callAdapterFactories);
            callAdapterFactories.add(new DefaultCallAdapterFactory(callBackExecutor));
            // 配置默认命名空间
            if (nameSpace == null || nameSpace.length() == 0) {
                nameSpace = "http://tempuri.org/";
            }
            return new Retrofit(
                    retrofitCallFactory,
                    baseUrl,
                    baseUrlEnd,
                    nameSpace,
                    header,
                    parameters,
                    requestInterceptor,
                    responseInterceptor,
                    exceptionInterceptor,
                    unmodifiableList(converterFactories),
                    unmodifiableList(callAdapterFactories),
                    callBackExecutor);
        }

    }
}
